// Copyright 2011 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package com.google.api.ads.dfp.lib.client;

import com.google.api.ads.common.lib.auth.OAuthCompatible;
import com.google.api.ads.common.lib.client.AdsSession;
import com.google.api.ads.common.lib.client.Endpoint;
import com.google.api.ads.common.lib.conf.ConfigurationHelper;
import com.google.api.ads.common.lib.exception.ValidationException;
import com.google.api.client.auth.oauth.OAuthParameters;
import com.google.common.annotations.VisibleForTesting;

import org.apache.commons.configuration.Configuration;

import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;

/**
 * A {@code DfpSession} represents a single session of DFP use.
 *
 * @author Adam Rogal
 */
public class DfpSession implements AdsSession, OAuthCompatible {

  /**
   * Enum representing the endpoint server, aka environment.
   * @author Adam Rogal
   */
  public static enum Environment implements Endpoint {
    PRODUCTION("https://www.google.com/");

    private final String endpoint;

    private Environment(String endpoint) {
      this.endpoint = endpoint;
    }

    /**
     * @return the endpoint
     */
    public String getEndpoint() {
      return endpoint;
    }
  }

  private String endpoint;
  private String clientLoginToken;
  private String applicationName;
  private String networkCode;
  private OAuthParameters oAuthParameters;


  /**
   * Private constructor.
   */
  private DfpSession() {
  }

  /**
   * @return the userAgent
   */
  public String getApplicationName() {
    return applicationName;
  }

  /**
   * @return the oAuthParameters
   */
  public OAuthParameters getOAuthParameters() {
    return oAuthParameters;
  }

  /**
   * @return the clientLoginToken
   */
  public String getClientLoginToken() {
    return clientLoginToken;
  }

  /**
   * @param clientLoginToken the clientLoginToken to set
   */
  public void setClientLoginToken(String clientLoginToken) {
    this.clientLoginToken = clientLoginToken;
  }

  /**
   * @return the endpoint
   */
  public String getEndpoint() {
    return endpoint;
  }


  /**
   * Determines if the session is for the supplied
   * {@link DfpSession.Environment}.
   */
  public boolean isEnvironment(Endpoint environment) {
    return environment.getEndpoint().equals(this.endpoint);
  }

  /**
   * @return the networkCode
   */
  public String getNetworkCode() {
    return networkCode;
  }

  /**
   * @param networkCode the networkCode to set
   */
  public void setNetworkCode(String networkCode) {
    this.networkCode = networkCode;
  }

  /**
   * Builder for DfpSession.
   */
  public static class Builder implements
      com.google.api.ads.common.lib.utils.Builder<DfpSession> {

    private DfpSession dfpSession;
    private String environmentString;
    @VisibleForTesting
    ConfigurationHelper configHelper;

    /**
     * Constructor.
     */
    public Builder() {
      this.dfpSession = new DfpSession();
      this.configHelper = new ConfigurationHelper();
    }

    @VisibleForTesting
    Builder(ConfigurationHelper configHelper) {
      this.dfpSession = new DfpSession();
      this.configHelper = configHelper;
    }

    /* (non-Javadoc)
     * @see com.google.api.ads.common.lib.utils.Builder#fromFile()
     */
    public Builder fromFile() {
      return fromFile(Builder.DEFAULT_CONFIGURATION_FILENAME);
    }

    /* (non-Javadoc)
     * @see com.google.api.ads.common.lib.utils.Builder#fromFile(java.lang.String)
     */
    public Builder fromFile(String path) {
      return from(configHelper.fromFile(path));
    }

    /* (non-Javadoc)
     * @see com.google.api.ads.common.lib.utils.Builder#fromFile(java.io.File)
     */
    public Builder fromFile(File path) {
      return from(configHelper.fromFile(path));
    }

    /* (non-Javadoc)
     * @see com.google.api.ads.common.lib.utils.Builder#fromFile(java.net.URL)
     */
    public Builder fromFile(URL path) {
      return from(configHelper.fromFile(path));
    }

    /**
     * Reads properties from the provided {@link Configuration} object.<br><br>
     * Known properties:
     * <ul>
     * <li>api.dfp.clientLoginToken</li>
     * <li>api.dfp.applicationName</li>
     * <li>api.dfp.networkCode</li>
     * <li>api.dfp.environment - e.g. production or sandbox/</li>
     * </ul>
     *
     * @param config
     * @return Builder populated from the Configuration
     */
    public Builder from(Configuration config) {
      dfpSession.clientLoginToken = config.getString("api.dfp.clientLoginToken", null);
      dfpSession.applicationName = config.getString("api.dfp.applicationName", null);
      dfpSession.networkCode = config.getString("api.dfp.networkCode", null);
      dfpSession.endpoint = config.getString("api.dfp.endpoint", null);
      environmentString = config.getString("api.dfp.environment", null);

      return this;
    }

    /**
     * Includes hard-coded ClientLogin token that will be used instead of
     * fetching a new one.
     */
    public Builder withClientLoginToken(String clientLoginToken) {
      dfpSession.clientLoginToken = clientLoginToken;
      return this;
    }

    /**
     * Includes OAuth parameters to be used for OAuth authentication.
     */
    public Builder withOAuthParameters(OAuthParameters oAuthParameters) {
      dfpSession.oAuthParameters = oAuthParameters;
      return this;
    }

    /**
     * Includes network code.
     */
    public Builder withNetworkCode(String networkCode) {
      dfpSession.networkCode = networkCode;
      return this;
    }

    /**
     * Includes application name.
     */
    public Builder withApplicationName(String applicationName) {
      dfpSession.applicationName = applicationName;
      return this;
    }

    /**
     * Includes the specified environment, e.g.
     * {@link Environment#PRODUCTION}
     *
     * The environment and endpoint in DFP are currently synonymous. Setting an
     * environment will clear out any endpoint you have previously set.
     */
    public Builder withEnvironment(Environment environment) {
      dfpSession.endpoint = environment.getEndpoint();
      environmentString = null;
      return this;
    }

    /**
     * Override the endpoint server.
     *
     * The environment and endpoint in DFP are currently synonymous. Setting an
     * endpoint will clear out any environment you have previously set.
     */
    public Builder withEndpoint(String endpoint) {
      dfpSession.endpoint = endpoint;
      environmentString = null;
      return this;
    }

    /**
     * Builds the {@code DfpSession}.
     *
     * @return the built {@code DfpSession}
     * @throws ValidationException if the {@code DfpSession} did not validate
     */
    public DfpSession build() throws ValidationException {
      validate(dfpSession);
      return dfpSession;
    }

    /**
     * Validates the DFP session.
     */
    private void validate(DfpSession dfpSession) throws ValidationException {
      if (environmentString != null) {
        try {
          withEnvironment(Environment.valueOf(environmentString.toUpperCase()));
        } catch (IllegalArgumentException e) {
          throw new ValidationException(String.format("Environment [%s] not recognized.",
              environmentString), "api.dfp.environment", e);
        }
      }

      boolean usingClientLogin = dfpSession.getClientLoginToken() != null;

      // Check for valid OAuth or ClientLogin information.
      if (usingClientLogin && dfpSession.getOAuthParameters() != null) {
        throw new ValidationException("Both ClientLogin authetication (clientLoginToken) and"
            + " OAuth authentication cannot be used at the same time.", "");
      }

      // Check for at least one authentication mechanism.
      if (!usingClientLogin && dfpSession.getOAuthParameters() == null) {
        throw new ValidationException(
            "Either ClientLogin authetication (email/password or clientLoginToken) or"
                + " OAuth authentication must be used.", "");
      }

      // Make sure they specify an environment.
      try {
        new URL(dfpSession.getEndpoint());
      } catch (MalformedURLException e) {
        throw new ValidationException("Endpoint is required and must be a valid URL.",
            "endpoint", e);
      }
    }
  }
}
